# Copyright (c) 2015-2025 Vector 35 Inc
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

import os
import ctypes
import traceback
import warnings
from typing import List, Dict, Optional, Tuple, Any

# Binary Ninja components
import binaryninja
from . import _binaryninjacore as core
from . import types
from . import typeparser
from . import callingconvention
from . import typelibrary
from . import architecture
from . import typecontainer
from . import binaryview
from .log import log_error_for_exception


class _PlatformMetaClass(type):
	def __iter__(self):
		binaryninja._init_plugins()
		count = ctypes.c_ulonglong()
		platforms = core.BNGetPlatformList(count)
		assert platforms is not None, "core.BNGetPlatformList returned None"
		try:
			for i in range(0, count.value):
				yield CorePlatform(handle=core.BNNewPlatformReference(platforms[i]))
		finally:
			core.BNFreePlatformList(platforms, count.value)

	def __getitem__(cls, value):
		binaryninja._init_plugins()
		platform = core.BNGetPlatformByName(str(value))
		if platform is None:
			raise KeyError("'%s' is not a valid platform" % str(value))
		return CorePlatform(handle=platform)

	def __contains__(cls: '_PlatformMetaClass', name: object) -> bool:
		if not isinstance(name, str):
			return False
		try:
			cls[name]
			return True
		except KeyError:
			return False

	def get(cls: '_PlatformMetaClass', name: str, default: Any = None) -> Optional['Platform']:
		try:
			return cls[name]
		except KeyError:
			if default is not None:
				return default
			return None


class Platform(metaclass=_PlatformMetaClass):
	"""
	``class Platform`` contains all information related to the execution environment of the binary, mainly the
	calling conventions used, the operating system, and the architecture.

    The following example showing the live list of platforms may not match your list. Some platforms are included only
    with specific versions of Binary Ninja and others are installed by plugins. The bare architecture version of the
    platform does not include any default ABI/calling convention::

		>>> list(Platform)
		[<platform: Solana>, <platform: decree-x86>, <platform: efi-aarch64>, <platform: efi-windows-aarch64>,
		<platform: efi-x86>, <platform: efi-windows-x86>, <platform: efi-x86_64>, <platform: efi-windows-x86_64>,
		<platform: efi-armv7>, <platform: efi-thumb2>, <platform: freebsd-aarch64>, <platform: freebsd-x86>,
		<platform: freebsd-x86_64>, <platform: freebsd-armv7>, <platform: freebsd-thumb2>, <platform: ios-aarch64>,
		<platform: ios-armv7>, <platform: ios-thumb2>, <platform: ios-kernel-aarch64>, <platform: ios-kernel-armv7>,
		<platform: ios-kernel-thumb2>, <platform: linux-aarch64>, <platform: linux-mcore_le>,
		<platform: linux-mcore_be>, <platform: linux-csky_le_v1>, <platform: linux-csky_le>, <platform: linux-armv7eb>,
		<platform: linux-thumb2eb>, <platform: linux-mips>, <platform: linux-mipsel>, <platform: linux-mips3>,
		<platform: linux-mipsel3>, <platform: linux-mips64>, <platform: linux-cnmips64>, <platform: linux-ppc32>,
		<platform: linux-ppc64>, <platform: linux-ppc32_le>, <platform: linux-ppc64_le>, <platform: linux-rv32gc>,
		<platform: linux-rv64gc>, <platform: linux-x86>, <platform: linux-x86_64>, <platform: linux-armv7>,
		<platform: linux-thumb2>, <platform: mac-aarch64>, <platform: mac-x86>, <platform: mac-x86_64>,
		<platform: mac-armv7>, <platform: mac-thumb2>, <platform: mac-kernel-aarch64>, <platform: mac-kernel-x86>,
		<platform: mac-kernel-x86_64>, <platform: mac-kernel-armv7>, <platform: mac-kernel-thumb2>,
		<platform: vxworks-aarch64>, <platform: vxworks-mips32>, <platform: vxworks-mipsel32>,
		<platform: vxworks-mips64>, <platform: vxworks-cavium-mips64>, <platform: vxworks-ppc32>,
		<platform: vxworks-ppc64>, <platform: vxworks-rv32gc>, <platform: vxworks-rv64gc>, <platform: vxworks-x86>,
		<platform: vxworks-x86_64>, <platform: vxworks-armv7>, <platform: vxworks-thumb2>, <platform: windows-aarch64>,
		<platform: windows-x86>, <platform: windows-x86_64>, <platform: windows-armv7>, <platform: windows-thumb2>,
		<platform: windows-kernel-windows-aarch64>, <platform: windows-kernel-x86>, <platform: windows-kernel-x86_64>]
		>>> thumb2 = Platform["thumb2"]

	"""
	name = None
	type_file_path = None  # path to platform types file
	type_include_dirs = []  # list of directories available to #include from type_file_path
	global_regs = [] # list of global registers. if empty, it populated with the arch global reg list
	global_reg_types = {} # opportunity for plugin to provide default types for the entry value of global registers

	_registered_platforms = []

	def __init__(self, arch: Optional['architecture.Architecture'] = None, handle=None):
		if handle is None:
			if arch is None:
				raise ValueError("platform must have an associated architecture")
			assert self.__class__.name is not None, "Can not instantiate Platform directly, you probably want arch.standalone_platform"
			_arch = arch
			if len(self.global_regs) == 0:
				self.__dict__["global_regs"] = arch.global_regs
			self._cb = core.BNCustomPlatform()
			self._cb.context = 0
			self._cb.init = self._cb.init.__class__(self._init)
			self._cb.viewInit = self._cb.viewInit.__class__(self._view_init)
			self._cb.getGlobalRegisters = self._cb.getGlobalRegisters.__class__(self._get_global_regs)
			self._cb.freeRegisterList = self._cb.freeRegisterList.__class__(self._free_register_list)
			self._cb.getGlobalRegisterType = self._cb.getGlobalRegisterType.__class__(self._get_global_reg_type)
			self._cb.getAddressSize = self._cb.getAddressSize.__class__(self._get_address_size)
			self._cb.adjustTypeParserInput = self._cb.adjustTypeParserInput.__class__(self._adjust_type_parser_input)
			self._cb.freeTypeParserInput = self._cb.freeTypeParserInput.__class__(self._free_type_parser_input)
			self._pending_reg_lists = {}
			self._pending_parser_input_lists = {}
			if self.__class__.type_file_path is None:
				_handle = core.BNCreateCustomPlatform(arch.handle, self.__class__.name, self._cb)
				assert _handle is not None
			else:
				dir_buf = (ctypes.c_char_p * len(self.__class__.type_include_dirs))()
				for (i, dir) in enumerate(self.__class__.type_include_dirs):
					dir_buf[i] = dir.encode('charmap')
				_handle = core.BNCreateCustomPlatformWithTypes(
				    arch.handle, self.__class__.name, self._cb, self.__class__.type_file_path, dir_buf,
				    len(self.__class__.type_include_dirs)
				)
				assert _handle is not None
				self.__class__._registered_platforms.append(self)
		else:
			if type(self) is Platform:
				binaryninja.log_warn(
					":py:func:`Platform(handle=...)` is deprecated, use :py:func:`CorePlatform._from_cache(handle=...)`"
				)
			_handle = handle
			_arch = architecture.CoreArchitecture._from_cache(core.BNGetPlatformArchitecture(_handle))
			count = ctypes.c_ulonglong()
			regs = core.BNGetPlatformGlobalRegisters(handle, count)
			result = []
			for i in range(0, count.value):
				result.append(_arch.get_reg_name(regs[i]))
			core.BNFreeRegisterList(regs)
			self.__dict__["global_regs"] = result
		assert _handle is not None
		assert _arch is not None
		self.handle: ctypes.POINTER(core.BNPlatform) = _handle
		self._arch = _arch
		self._name = None
		self._address_size = core.BNGetPlatformAddressSize(_handle)

	def _init(self, ctxt):
		pass

	def _view_init(self, ctxt, view):
		try:
			view_obj = binaryview.BinaryView(handle=core.BNNewViewReference(view))
			self.view_init(view)
		except:
			log_error_for_exception("Unhandled Python exception in Platform._view_init")

	def _get_global_regs(self, ctxt, count):
		try:
			regs = self.global_regs
			count[0] = len(regs)
			reg_buf = (ctypes.c_uint * len(regs))()
			for i in range(0, len(regs)):
				reg_buf[i] = self.arch.regs[regs[i]].index
			result = ctypes.cast(reg_buf, ctypes.c_void_p)
			self._pending_reg_lists[result.value] = (result, reg_buf)
			return result.value
		except:
			log_error_for_exception("Unhandled Python exception in Platform._get_global_regs")
			count[0] = 0
			return None

	def _free_register_list(self, ctxt, regs, size):
		try:
			buf = ctypes.cast(regs, ctypes.c_void_p)
			if buf.value not in self._pending_reg_lists:
				raise ValueError("freeing register list that wasn't allocated")
			del self._pending_reg_lists[buf.value]
		except:
			log_error_for_exception("Unhandled Python exception in Platform._free_register_list")

	def _get_global_reg_type(self, ctxt, reg):
		try:
			reg_name = self.arch.get_reg_name(reg)
			if reg_name in self.global_reg_types:
				type_obj = self.global_reg_types[reg_name]
				handle = core.BNNewTypeReference(type_obj.handle)
				return ctypes.cast(handle, ctypes.c_void_p).value
			return None
		except:
			log_error_for_exception("Unhandled Python exception in Platform._get_global_reg_type")
			return None

	def _get_address_size(self, ctxt):
		try:
			return self.address_size
		except:
			return self.arch.address_size

	def _adjust_type_parser_input(
			self,
			ctxt,
			parser,
			arguments_in,
			arguments_len_in,
			source_file_names_in,
			source_file_values_in,
			source_files_len_in,
			arguments_out,
			arguments_len_out,
			source_file_names_out,
			source_file_values_out,
			source_files_len_out
	):
		try:
			parser_py = typeparser.TypeParser(handle=parser)

			arguments = []
			for i in range(arguments_len_in):
				arguments.append(core.pyNativeStr(arguments_in[i]))

			source_files = []
			for i in range(source_files_len_in):
				source_files.append((core.pyNativeStr(source_file_names_in[i]), core.pyNativeStr(source_file_values_in[i])))

			arguments, source_files = self.adjust_type_parser_input(parser_py, arguments, source_files)

			arguments_len_out[0] = len(arguments)
			arguments_buf = (ctypes.c_char_p * len(arguments))()
			for i, r in enumerate(arguments):
				arguments_buf[i] = core.cstr(r)
			arguments_ptr = ctypes.cast(arguments_buf, ctypes.c_void_p)
			arguments_out[0] = arguments_buf
			self._pending_parser_input_lists[arguments_ptr.value] = (arguments_ptr.value, arguments_buf)

			source_files_len_out[0] = len(source_files)
			source_file_names_buf = (ctypes.c_char_p * len(source_files))()
			source_file_values_buf = (ctypes.c_char_p * len(source_files))()
			for i, (name, value) in enumerate(source_files):
				source_file_names_buf[i] = core.cstr(name)
				source_file_values_buf[i] = core.cstr(value)
			source_file_names_ptr = ctypes.cast(source_file_names_buf, ctypes.c_void_p)
			source_file_names_out[0] = source_file_names_buf
			source_file_values_ptr = ctypes.cast(source_file_values_buf, ctypes.c_void_p)
			source_file_values_out[0] = source_file_values_buf
			self._pending_parser_input_lists[source_file_names_ptr.value] = (source_file_names_ptr.value, source_file_names_buf)
			self._pending_parser_input_lists[source_file_values_ptr.value] = (source_file_values_ptr.value, source_file_values_buf)

		except:
			arguments_out[0] = None
			arguments_len_out[0] = 0
			source_file_names_out[0] = None
			source_file_values_out[0] = None
			source_files_len_out[0] = 0
			traceback.print_exc()

	def _free_type_parser_input(
			self,
			ctxt,
			arguments,
			arguments_len,
			source_file_names,
			source_file_values,
			source_files_len
	):
		try:
			buf = ctypes.cast(arguments, ctypes.c_void_p)
			if buf.value is not None:
				if buf.value not in self._pending_parser_input_lists:
					raise ValueError("freeing arguments list that wasn't allocated")
				del self._pending_parser_input_lists[buf.value]

			buf = ctypes.cast(source_file_names, ctypes.c_void_p)
			if buf.value is not None:
				if buf.value not in self._pending_parser_input_lists:
					raise ValueError("freeing source_file_names list that wasn't allocated")
				del self._pending_parser_input_lists[buf.value]

			buf = ctypes.cast(source_file_values, ctypes.c_void_p)
			if buf.value is not None:
				if buf.value not in self._pending_parser_input_lists:
					raise ValueError("freeing source_file_values list that wasn't allocated")
				del self._pending_parser_input_lists[buf.value]
		except:
			log_error_for_exception("Unhandled Python exception in Platform._free_type_parser_input")

	def adjust_type_parser_input(
			self,
			parser: 'typeparser.TypeParser',
			arguments: List[str],
			source_files: List[Tuple[str, str]]
	) -> Tuple[List[str], List[Tuple[str, str]]]:
		"""
		Modify the arguments passed to the Type Parser with Platform-specific features.

		:param parser: Type Parser instance
		:param arguments: Default arguments passed to the parser
		:param source_files: Source file names and contents
		:return: A tuple of (modified arguments, modified source files)
		"""
		return arguments, source_files

	def __del__(self):
		if core is not None:
			core.BNFreePlatform(self.handle)

	def __repr__(self):
		return f"<platform: {self.name}>"

	def __str__(self):
		return self.name

	def __eq__(self, other: 'Platform'):
		if not isinstance(other, self.__class__):
			return NotImplemented
		return ctypes.addressof(self.handle.contents) == ctypes.addressof(other.handle.contents)

	def __ne__(self, other: 'Platform'):
		if not isinstance(other, self.__class__):
			return NotImplemented
		return not (self == other)

	def __hash__(self):
		return hash(ctypes.addressof(self.handle.contents))

	@property
	def name(self) -> str:
		if self._name is None:
			self._name = core.BNGetPlatformName(self.handle)
		return self._name

	@property
	def address_size(self) -> int:
		return self._address_size

	@classmethod
	@property
	def os_list(cls) -> List[str]:
		binaryninja._init_plugins()
		count = ctypes.c_ulonglong()
		platforms = core.BNGetPlatformOSList(count)
		assert platforms is not None, "core.BNGetPlatformOSList returned None"
		result:List[str] = []
		for i in range(count.value):
			result.append(str(platforms[i]))
		core.BNFreePlatformOSList(platforms, count.value)
		return result

	@classmethod
	def get_list(cls, os=None, arch=None):
		binaryninja._init_plugins()
		count = ctypes.c_ulonglong()
		if os is None:
			platforms = core.BNGetPlatformList(count)
			assert platforms is not None, "core.BNGetPlatformList returned None"
		elif arch is None:
			platforms = core.BNGetPlatformListByOS(os, count)
			assert platforms is not None, "core.BNGetPlatformListByOS returned None"
		else:
			platforms = core.BNGetPlatformListByArchitecture(arch.handle, count)
			assert platforms is not None, "core.BNGetPlatformListByArchitecture returned None"
		result = []
		for i in range(0, count.value):
			result.append(CorePlatform._from_cache(core.BNNewPlatformReference(platforms[i])))
		core.BNFreePlatformList(platforms, count.value)
		return result

	@property
	def default_calling_convention(self):
		"""
		Default calling convention.

		:getter: returns a CallingConvention object for the default calling convention.
		:setter: sets the default calling convention
		:type: CallingConvention
		"""
		result = core.BNGetPlatformDefaultCallingConvention(self.handle)
		if result is None:
			return None
		return callingconvention.CallingConvention(handle=result)

	@default_calling_convention.setter
	def default_calling_convention(self, value):
		core.BNRegisterPlatformDefaultCallingConvention(self.handle, value.handle)

	@property
	def cdecl_calling_convention(self):
		"""
		CallingConvention object for the cdecl calling convention
		"""
		result = core.BNGetPlatformCdeclCallingConvention(self.handle)
		if result is None:
			return None
		return callingconvention.CallingConvention(handle=result)

	@cdecl_calling_convention.setter
	def cdecl_calling_convention(self, value):
		"""
		Sets the cdecl calling convention
		"""
		core.BNRegisterPlatformCdeclCallingConvention(self.handle, value.handle)

	@property
	def stdcall_calling_convention(self):
		"""
		CallingConvention object for the stdcall calling convention
		"""
		result = core.BNGetPlatformStdcallCallingConvention(self.handle)
		if result is None:
			return None
		return callingconvention.CallingConvention(handle=result)

	@stdcall_calling_convention.setter
	def stdcall_calling_convention(self, value):
		"""
		Sets the stdcall calling convention
		"""
		core.BNRegisterPlatformStdcallCallingConvention(self.handle, value.handle)

	@property
	def fastcall_calling_convention(self):
		"""
		CallingConvention object for the fastcall calling convention
		"""
		result = core.BNGetPlatformFastcallCallingConvention(self.handle)
		if result is None:
			return None
		return callingconvention.CallingConvention(handle=result)

	@fastcall_calling_convention.setter
	def fastcall_calling_convention(self, value):
		"""
		Sets the fastcall calling convention
		"""
		core.BNRegisterPlatformFastcallCallingConvention(self.handle, value.handle)

	@property
	def system_call_convention(self):
		"""
		CallingConvention object for the system call convention
		"""
		result = core.BNGetPlatformSystemCallConvention(self.handle)
		if result is None:
			return None
		return callingconvention.CallingConvention(handle=result)

	@system_call_convention.setter
	def system_call_convention(self, value):
		"""
		Sets the system call convention
		"""
		core.BNSetPlatformSystemCallConvention(self.handle, value.handle)

	@property
	def calling_conventions(self):
		"""
		List of platform CallingConvention objects (read-only)

		:getter: returns the list of supported CallingConvention objects
		:type: list(CallingConvention)
		"""
		count = ctypes.c_ulonglong()
		cc = core.BNGetPlatformCallingConventions(self.handle, count)
		assert cc is not None, "core.BNGetPlatformCallingConventions returned None"
		result = []
		for i in range(0, count.value):
			result.append(callingconvention.CallingConvention(handle=core.BNNewCallingConventionReference(cc[i])))
		core.BNFreeCallingConventionList(cc, count.value)
		return result

	def get_global_register_type(self, reg: 'architecture.RegisterType'):
		reg = self.arch.get_reg_index(reg)
		type_obj = core.BNGetPlatformGlobalRegisterType(self.handle, reg)
		if type_obj is None:
			return None
		return types.Type.create(type_obj, platform=self)

	def view_init(self, view):
		pass
		#raise NotImplementedError


	@property
	def types(self):
		"""List of platform-specific types (read-only)"""
		count = ctypes.c_ulonglong(0)
		type_list = core.BNGetPlatformTypes(self.handle, count)
		assert type_list is not None, "core.BNGetPlatformTypes returned None"
		result = {}
		for i in range(0, count.value):
			name = types.QualifiedName._from_core_struct(type_list[i].name)
			result[name] = types.Type.create(core.BNNewTypeReference(type_list[i].type), platform=self)
		core.BNFreeTypeAndNameList(type_list, count.value)
		return result

	@property
	def variables(self):
		"""List of platform-specific variable definitions (read-only)"""
		count = ctypes.c_ulonglong(0)
		type_list = core.BNGetPlatformVariables(self.handle, count)
		assert type_list is not None, "core.BNGetPlatformVariables returned None"
		result = {}
		for i in range(0, count.value):
			name = types.QualifiedName._from_core_struct(type_list[i].name)
			result[name] = types.Type.create(core.BNNewTypeReference(type_list[i].type), platform=self)
		core.BNFreeTypeAndNameList(type_list, count.value)
		return result

	@property
	def functions(self):
		"""List of platform-specific function definitions (read-only)"""
		count = ctypes.c_ulonglong(0)
		type_list = core.BNGetPlatformFunctions(self.handle, count)
		assert type_list is not None, "core.BNGetPlatformFunctions returned None"
		result = {}
		for i in range(0, count.value):
			name = types.QualifiedName._from_core_struct(type_list[i].name)
			result[name] = types.Type.create(core.BNNewTypeReference(type_list[i].type), platform=self)
		core.BNFreeTypeAndNameList(type_list, count.value)
		return result

	@property
	def system_calls(self):
		"""List of system calls for this platform (read-only)"""
		count = ctypes.c_ulonglong(0)
		call_list = core.BNGetPlatformSystemCalls(self.handle, count)
		assert call_list is not None, "core.BNGetPlatformSystemCalls returned None"
		result = {}
		for i in range(0, count.value):
			name = types.QualifiedName._from_core_struct(call_list[i].name)
			t = types.Type.create(core.BNNewTypeReference(call_list[i].type), platform=self)
			result[call_list[i].number] = (name, t)
		core.BNFreeSystemCallList(call_list, count.value)
		return result

	@property
	def type_libraries(self) -> List['typelibrary.TypeLibrary']:
		count = ctypes.c_ulonglong(0)
		libs = core.BNGetPlatformTypeLibraries(self.handle, count)
		assert libs is not None, "core.BNGetPlatformTypeLibraries returned None"
		result = []
		for i in range(0, count.value):
			result.append(typelibrary.TypeLibrary(core.BNNewTypeLibraryReference(libs[i])))
		core.BNFreeTypeLibraryList(libs, count.value)
		return result

	def get_type_libraries_by_name(self, name) -> List['typelibrary.TypeLibrary']:
		count = ctypes.c_ulonglong(0)
		libs = core.BNGetPlatformTypeLibrariesByName(self.handle, name, count)
		assert libs is not None, "core.BNGetPlatformTypeLibrariesByName returned None"
		result = []
		for i in range(0, count.value):
			result.append(typelibrary.TypeLibrary(core.BNNewTypeLibraryReference(libs[i])))
		core.BNFreeTypeLibraryList(libs, count.value)
		return result

	def register(self, os):
		"""
		``register`` registers the platform for given OS name.

		:param str os: OS name to register
		:rtype: None
		"""
		core.BNRegisterPlatform(os, self.handle)

	def register_calling_convention(self, cc):
		"""
		``register_calling_convention`` register a new calling convention.

		:param CallingConvention cc: a CallingConvention object to register
		:rtype: None
		"""
		core.BNRegisterPlatformCallingConvention(self.handle, cc.handle)

	def get_related_platform(self, arch):
		result = core.BNGetRelatedPlatform(self.handle, arch.handle)
		if not result:
			return None
		return CorePlatform._from_cache(handle=result)

	def get_related_platforms(self) -> List['Platform']:
		"""
		``get_related_platforms`` returns a list of all related platforms for a given platform

		:return:
		"""
		count = ctypes.c_ulonglong()
		platforms = core.BNGetRelatedPlatforms(self.handle, count)
		assert platforms is not None, "core.BNGetRelatedPlatforms returned None"
		result = []
		for i in range(0, count.value):
			result.append(CorePlatform._from_cache(core.BNNewPlatformReference(platforms[i])))
		core.BNFreePlatformList(platforms, count.value)
		return result

	def add_related_platform(self, arch, platform):
		core.BNAddRelatedPlatform(self.handle, arch.handle, platform.handle)

	def get_associated_platform_by_address(self, addr):
		new_addr = ctypes.c_ulonglong()
		new_addr.value = addr
		result = core.BNGetAssociatedPlatformByAddress(self.handle, new_addr)
		return CorePlatform._from_cache(result), new_addr.value

	@property
	def type_container(self) -> 'typecontainer.TypeContainer':
		"""
		Type Container for all registered types in the Platform.
		:return: Platform types Type Container
		"""
		return typecontainer.TypeContainer(core.BNGetPlatformTypeContainer(self.handle))

	def get_type_by_name(self, name):
		name = types.QualifiedName(name)._to_core_struct()
		obj = core.BNGetPlatformTypeByName(self.handle, name)
		if not obj:
			return None
		return types.Type.create(obj, platform=self)

	def get_variable_by_name(self, name):
		name = types.QualifiedName(name)._to_core_struct()
		obj = core.BNGetPlatformVariableByName(self.handle, name)
		if not obj:
			return None
		return types.Type.create(obj, platform=self)

	def get_function_by_name(self, name, exactMatch=False):
		name = types.QualifiedName(name)._to_core_struct()
		obj = core.BNGetPlatformFunctionByName(self.handle, name, exactMatch)
		if not obj:
			return None
		return types.Type.create(obj, platform=self)

	def get_system_call_name(self, number):
		return core.BNGetPlatformSystemCallName(self.handle, number)

	def get_system_call_type(self, number):
		obj = core.BNGetPlatformSystemCallType(self.handle, number)
		if not obj:
			return None
		return types.Type.create(obj, platform=self)

	def generate_auto_platform_type_id(self, name):
		name = types.QualifiedName(name)._to_core_struct()
		return core.BNGenerateAutoPlatformTypeId(self.handle, name)

	def generate_auto_platform_type_ref(self, type_class, name):
		type_id = self.generate_auto_platform_type_id(name)
		return types.NamedTypeReferenceBuilder.create(type_class, type_id, name)

	def get_auto_platform_type_id_source(self):
		return core.BNGetAutoPlatformTypeIdSource(self.handle)

	def parse_types_from_source(
	    self, source, filename=None, include_dirs: Optional[List[str]] = None, auto_type_source=None
	):
		"""
		``parse_types_from_source`` parses the source string and any needed headers searching for them in
		the optional list of directories provided in ``include_dirs``. Note that this API does not allow
		the source to rely on existing types that only exist in a specific view. Use :py:meth:`BinaryView.parse_type_string` instead.

		:param str source: source string to be parsed
		:param str filename: optional source filename
		:param include_dirs: optional list of string filename include directories
		:type include_dirs: list(str)
		:param str auto_type_source: optional source of types if used for automatically generated types
		:return: :py:class:`BasicTypeParserResult` (a SyntaxError is thrown on parse error)
		:rtype: BasicTypeParserResult
		:Example:

			>>> platform.parse_types_from_source('int foo;\\nint bar(int x);\\nstruct bas{int x,y;};\\n')
			({types: {'bas': <type: struct bas>}, variables: {'foo': <type: int32_t>}, functions:{'bar':
			<type: int32_t(int32_t x)>}}, '')
			>>>
		"""

		if filename is None:
			filename = "input"
		if not isinstance(source, str):
			raise AttributeError("Source must be a string")
		if include_dirs is None:
			include_dirs = []
		dir_buf = (ctypes.c_char_p * len(include_dirs))()
		for i in range(0, len(include_dirs)):
			dir_buf[i] = include_dirs[i].encode('charmap')
		parse = core.BNTypeParserResult()
		errors = ctypes.c_char_p()
		result = core.BNParseTypesFromSource(
		    self.handle, source, filename, parse, errors, dir_buf, len(include_dirs), auto_type_source
		)
		assert errors.value is not None, "core.BNParseTypesFromSource returned errors set to None"
		error_str = errors.value.decode("utf-8")
		core.free_string(errors)
		if not result:
			raise SyntaxError(error_str)
		type_dict: Dict[types.QualifiedName, types.Type] = {}
		variables: Dict[types.QualifiedName, types.Type] = {}
		functions: Dict[types.QualifiedName, types.Type] = {}
		for i in range(0, parse.typeCount):
			name = types.QualifiedName._from_core_struct(parse.types[i].name)
			type_dict[name] = types.Type.create(core.BNNewTypeReference(parse.types[i].type), platform=self)
		for i in range(0, parse.variableCount):
			name = types.QualifiedName._from_core_struct(parse.variables[i].name)
			variables[name] = types.Type.create(core.BNNewTypeReference(parse.variables[i].type), platform=self)
		for i in range(0, parse.functionCount):
			name = types.QualifiedName._from_core_struct(parse.functions[i].name)
			functions[name] = types.Type.create(core.BNNewTypeReference(parse.functions[i].type), platform=self)
		core.BNFreeTypeParserResult(parse)
		return typeparser.BasicTypeParserResult(type_dict, variables, functions)

	def parse_types_from_source_file(self, filename, include_dirs: Optional[List[str]] = None, auto_type_source=None):
		"""
		``parse_types_from_source_file`` parses the source file ``filename`` and any needed headers searching for them in
		the optional list of directories provided in ``include_dirs``. Note that this API does not allow
		the source to rely on existing types that only exist in a specific view. Use :py:meth:`BinaryView.parse_type_string` instead.

		:param str filename: filename of file to be parsed
		:param include_dirs: optional list of string filename include directories
		:type include_dirs: list(str)
		:param str auto_type_source: optional source of types if used for automatically generated types
		:return: :py:class:`BasicTypeParserResult` (a SyntaxError is thrown on parse error)
		:rtype: BasicTypeParserResult
		:Example:

			>>> file = "/Users/binja/tmp.c"
			>>> open(file).read()
			'int foo;\\nint bar(int x);\\nstruct bas{int x,y;};\\n'
			>>> platform.parse_types_from_source_file(file)
			({types: {'bas': <type: struct bas>}, variables: {'foo': <type: int32_t>}, functions:
			{'bar': <type: int32_t(int32_t x)>}}, '')
			>>>
		"""
		if not (isinstance(filename, str) and os.path.isfile(filename) and os.access(filename, os.R_OK)):
			raise AttributeError("File {} doesn't exist or isn't readable".format(filename))
		if include_dirs is None:
			include_dirs = []
		dir_buf = (ctypes.c_char_p * len(include_dirs))()
		for i in range(0, len(include_dirs)):
			dir_buf[i] = include_dirs[i].encode('charmap')
		parse = core.BNTypeParserResult()
		errors = ctypes.c_char_p()
		result = core.BNParseTypesFromSourceFile(
		    self.handle, filename, parse, errors, dir_buf, len(include_dirs), auto_type_source
		)
		assert errors.value is not None, "core.BNParseTypesFromSourceFile returned errors set to None"
		error_str = errors.value.decode("utf-8")
		core.free_string(errors)
		if not result:
			raise SyntaxError(error_str)
		type_dict = {}
		variables = {}
		functions = {}
		for i in range(0, parse.typeCount):
			name = types.QualifiedName._from_core_struct(parse.types[i].name)
			type_dict[name] = types.Type.create(core.BNNewTypeReference(parse.types[i].type), platform=self)
		for i in range(0, parse.variableCount):
			name = types.QualifiedName._from_core_struct(parse.variables[i].name)
			variables[name] = types.Type.create(core.BNNewTypeReference(parse.variables[i].type), platform=self)
		for i in range(0, parse.functionCount):
			name = types.QualifiedName._from_core_struct(parse.functions[i].name)
			functions[name] = types.Type.create(core.BNNewTypeReference(parse.functions[i].type), platform=self)
		core.BNFreeTypeParserResult(parse)
		return typeparser.BasicTypeParserResult(type_dict, variables, functions)

	@property
	def arch(self):
		return self._arch


_platform_cache = {}


class CorePlatform(Platform):
	def __init__(self, handle: core.BNPlatform):
		super(CorePlatform, self).__init__(handle=handle)
		if type(self) is CorePlatform:
			global _platform_cache
			_platform_cache[ctypes.addressof(handle.contents)] = self

	@classmethod
	def _from_cache(cls, handle) -> 'Platform':
		"""
		Look up a platform from a given BNPlatform handle
		:param handle: BNPlatform pointer
		:return: Platform instance responsible for this handle
		"""
		global _platform_cache
		return _platform_cache.get(ctypes.addressof(handle.contents)) or cls(handle)

	def adjust_type_parser_input(
			self,
			parser: 'typeparser.TypeParser',
			arguments: List[str],
			source_files: List[Tuple[str, str]]
	) -> Tuple[List[str], List[Tuple[str, str]]]:

		arguments_in = (ctypes.c_char_p * len(arguments))()
		for i, argument in enumerate(arguments):
			arguments_in[i] = core.cstr(argument)

		source_file_names_in = (ctypes.c_char_p * len(source_files))()
		source_file_names_out = (ctypes.c_char_p * len(source_files))()
		source_file_values_in = (ctypes.c_char_p * len(source_files))()
		source_file_values_out = (ctypes.c_char_p * len(source_files))()
		for i, (name, value) in enumerate(source_files):
			source_file_names_in[i] = core.cstr(name)
			source_file_values_in[i] = core.cstr(value)

		arguments_out = ctypes.POINTER(ctypes.c_char_p)()
		arguments_len_out = (ctypes.c_size_t)()
		source_file_names_out = ctypes.POINTER(ctypes.c_char_p)()
		source_file_values_out = ctypes.POINTER(ctypes.c_char_p)()
		source_files_len_out = (ctypes.c_size_t)()

		core.BNPlatformAdjustTypeParserInput(
			self.handle,
			parser.handle,
			arguments_in,
			len(arguments),
			source_file_names_in,
			source_file_values_in,
			len(source_files),
			arguments_out,
			arguments_len_out,
			source_file_names_out,
			source_file_values_out,
			source_files_len_out
		)

		result_arguments = []
		for i in range(arguments_len_out.value):
			result_arguments.append(core.pyNativeStr(arguments_out[i]))

		result_source_files = []
		for i in range(source_files_len_out.value):
			result_source_files.append((
				core.pyNativeStr(source_file_names_out[i]),
				core.pyNativeStr(source_file_values_out[i]),
			))

		core.BNFreeStringList(arguments_out, arguments_len_out.value)
		core.BNFreeStringList(source_file_names_out, source_files_len_out.value)
		core.BNFreeStringList(source_file_values_out, source_files_len_out.value)

		return result_arguments, result_source_files
